﻿using Microsoft.TeamFoundation;
using Microsoft.TeamFoundation.Client;
using Microsoft.TeamFoundation.Framework.Common;
using Microsoft.TeamFoundation.Framework.Server;
using Microsoft.TeamFoundation.Server.Core;
using Microsoft.TeamFoundation.WorkItemTracking.Client;
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Xml;

namespace GrantWitProvision
{
    class Program
    {
        static void Main(string[] args)
        {
            Dictionary<string, string> keyValueArguments;

            if (args == null || args.Length == 0 || args[0].Equals("/?") || args[0].Equals("-?") || !Parse(args, out keyValueArguments))
            {
                PrintUsage();
                return;
            }

            try
            {
                string tpcUrl = keyValueArguments["tpcUrl"];
                TfsTeamProjectCollection tfsCollection = new TfsTeamProjectCollection(new Uri(tpcUrl));

                string configDbConnectionString = string.Empty;
                if (keyValueArguments.ContainsKey("webConfig"))
                    configDbConnectionString = GetConfigDbConnectionStringFromWebConfig(Path.Combine(keyValueArguments["webConfig"]));
                if (keyValueArguments.ContainsKey("db"))
                    configDbConnectionString = keyValueArguments["db"];

                WorkItemStore witStore = tfsCollection.GetService<WorkItemStore>();

                using (DeploymentServiceHost deploymentServiceHost = CreateDeploymentServiceHost(configDbConnectionString))
                {
                    GrantWitProvisionPermission(deploymentServiceHost, tfsCollection.InstanceId, witStore.Projects);
                }

                Console.WriteLine("Done.");
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
        }

        private static void GrantWitProvisionPermission(DeploymentServiceHost serviceHost, Guid instanceId, ProjectCollection projects)
        {
            using (TeamFoundationRequestContext requestContext = CreateServicingContext(serviceHost, instanceId))
            {
                var identityService = requestContext.GetService<TeamFoundationIdentityService>();
                var securityService = requestContext.GetService<ITeamFoundationSecurityService>();
                var witProvisionNamespace = securityService.GetSecurityNamespace(requestContext.Elevate(), WitProvisionSecurity.NamespaceId);

                foreach (Project project in projects)
                {
                    Console.WriteLine(string.Format("Fixing WitProvision permissions for {0} Project Administrators...", project.Name));

                    var projectAdminGroup = identityService.ReadIdentity(requestContext, IdentitySearchFactor.AdministratorsGroup, project.Uri.ToString(), MembershipQuery.None, ReadIdentityOptions.None, null);
                    witProvisionNamespace.SetPermissions(requestContext,
                        string.Format("{0}{2}{1}{2}", WitProvisionSecurity.RootToken, project.Guid, WitProvisionSecurity.Separator), 
                        projectAdminGroup.Descriptor, 
                        WitProvisionSecurity.Admin, 
                        0, 
                        false);
                }

                Console.WriteLine("Fixing WitProvision permissions for Project Collection Administrators...");
                var tpcAdminGroup = identityService.ReadIdentity(requestContext, Microsoft.TeamFoundation.Framework.Common.IdentitySearchFactor.Identifier, GroupWellKnownSidConstants.NamespaceAdministratorsGroupSid);
                witProvisionNamespace.SetPermissions(requestContext, WitProvisionSecurity.RootToken, tpcAdminGroup.Descriptor, WitProvisionSecurity.Admin | WitProvisionSecurity.ManageLinkTypes, 0, false);
            }
        }

        private static DeploymentServiceHost CreateDeploymentServiceHost(string configDbConnectionString)
        {
            Console.WriteLine("Creating deployment service host...");

            TeamFoundationServiceHostProperties deploymentHostProperties = new TeamFoundationServiceHostProperties();
            deploymentHostProperties.ConnectionInfo = SqlConnectionInfoFactory.Create(configDbConnectionString, null, null);
            deploymentHostProperties.HostType = TeamFoundationHostType.Deployment | TeamFoundationHostType.Application;

            return new DeploymentServiceHost(deploymentHostProperties, true);
        }

        private static TeamFoundationRequestContext CreateServicingContext(DeploymentServiceHost deploymentServiceHost, Guid instanceId)
        {
            Console.WriteLine("Creating servicing context...");

            using (TeamFoundationRequestContext requestContext = deploymentServiceHost.CreateSystemContext(true))
            {
                TeamFoundationHostManagementService host = requestContext.GetService<TeamFoundationHostManagementService>();
                return host.BeginRequest(requestContext, instanceId, RequestContextType.ServicingContext);
            }
        }

        private static string GetConfigDbConnectionStringFromWebConfig(string pathWebConfig)
        {
            Console.WriteLine("Loading web.config...");

            if (!File.Exists(pathWebConfig))
            {
                throw new FileNotFoundException("web.config not found.", pathWebConfig);
            }

            XmlDocument webConfig = new XmlDocument();
            webConfig.Load(pathWebConfig);
            XmlNode dbNode = webConfig.SelectSingleNode("/configuration/appSettings/add[@key='applicationDatabase']/@value");
            if (dbNode == null || string.IsNullOrEmpty(dbNode.Value))
            {
                throw new InvalidDataException(string.Format("{0} doesn't have applicationDatabase or value is not set. This is a invalid web.config", pathWebConfig));
            }

            Console.WriteLine("Config DB connection string: {0}", dbNode.Value);

            return dbNode.Value;
        }

        private static bool Parse(string[] args, out Dictionary<string, string> keyValueArguments)
        {
            keyValueArguments = new Dictionary<string, string>();

            for (int i = 0; i < args.Length; ++i)
            {
                if (args[i].StartsWith("/c:", StringComparison.OrdinalIgnoreCase))
                {
                    keyValueArguments["tpcUrl"] = args[i].Substring(3);
                }
                else if (args[i].StartsWith("/w:", StringComparison.OrdinalIgnoreCase))
                {
                    keyValueArguments["webConfig"] = args[i].Substring(3);
                }
                else if (args[i].StartsWith("/d:", StringComparison.OrdinalIgnoreCase))
                {
                    keyValueArguments["db"] = args[i].Substring(3);
                }
                else
                {
                    return false;
                }
            }

            return true;
        }

        private static void PrintUsage()
        {
            StringBuilder sb = new StringBuilder();

            sb.AppendLine("Usage: GrantWitProvision /c:<collectionUrl> [/d:<connectionString>] [/w:<webConfigPath>]");
            sb.AppendLine();
            sb.AppendLine("/c:\t\tTFS collection Url.");
            sb.AppendLine("/d:\t\tTFS configuration DB connection string.");
            sb.AppendLine("/w:\t\tPath to TFS AT web.config.");
            sb.AppendLine();
            sb.AppendLine(@"Examples:");
            sb.AppendLine(@"GrantWitProvision /c:http://localhost:8080/tfs/DefaultCollection /d:""Data Source=.;Initial Catalog=Tfs_Configuration;Integrated Security=True""");
            sb.AppendLine(@"GrantWitProvision /c:http://localhost:8080/tfs/DefaultCollection /w:""c:\Program Files\Microsoft Team Foundation Server 12.0\Application Tier\Web Services\web.config""");

            Console.Write(sb.ToString());
        }
    }
}
